package com.ds.infrastructure.hyperspace.container.flow.handler.route;

import com.google.common.base.Charsets;
import com.ds.infrastructure.hyperspace.common.Container;
import com.ds.infrastructure.hyperspace.common.flow.FlowHandlerPipeline;
import com.ds.infrastructure.hyperspace.container.flow.context.HyperspaceDuplexFlowContext;
import com.ds.infrastructure.hyperspace.container.flow.handler.AbstractPreFlowHandler;
import com.ds.infrastructure.hyperspace.container.internal.operator.OperatorDefinition;
import io.netty.handler.codec.http.FullHttpRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.ds.infrastructure.hyperspace.container.internal.util.StringConstant.ASTERISK;

/**
 * usage eg:
 *  -   handler: httpFullRoutePreHandler
 *      handlerParams: [hostName1|hostName1, uri|body|headerName, (operatorName,operatorArgs)...]
 *
 * @author  Weishaoying
 */
@Slf4j
public class HttpFullRoutePreFlowHandler extends AbstractPreFlowHandler {

    private static final String BODY_PARAM_NAME = "body";

    private static final int INPUT_NAME_INDEX = 1;

    private static final int DEFAULT_HOST_SIZE = 5;

    private volatile boolean allHostMatch;

    private Map<String, String> hostMap = new ConcurrentHashMap<>(DEFAULT_HOST_SIZE);

    private volatile String inputName;

    private volatile List<OperatorDefinition> operatorDefinitionList;

    @Override
    public void handleInput(HyperspaceDuplexFlowContext duplexFlowContext, Object data) {
        if (data instanceof FullHttpRequest) {
            if (allHostMatch) {
                duplexFlowContext.setRouteMatch(true);
                return;
            }

            if (hostMap.get(duplexFlowContext.getHost()) == null) {
                return;
            }
            if (inputName != null && extractAndComputeFullInput(duplexFlowContext,
                    inputName, operatorDefinitionList) == null) {
                return;
            }
            duplexFlowContext.setRouteMatch(true);
        }

    }

    @Override
    public String handlerName() {
        return "httpFullRoutePreHandler";
    }

    /**
     *
     * @param container
     * @param handlerParams
     */
    @Override
    public void initHandlerParameters(Container container,
            FlowHandlerPipeline pipeline, String[] handlerParams) {
        String hostStr = handlerParams[0];
        if (ASTERISK.equals(hostStr)) {
            allHostMatch = true;
            pipeline.registerPipelineHost(ASTERISK);
        } else {
            if (hostStr.indexOf(OR_DELIMITER) == -1) {
                hostMap.put(hostStr, "");
                pipeline.registerPipelineHost(hostStr);
            } else {
                String[] hostStrArray = StringUtils.delimitedListToStringArray(
                        hostStr, OR_DELIMITER);
                for (String tempOneHostStr : hostStrArray) {
                    hostMap.put(tempOneHostStr, "");
                    pipeline.registerPipelineHost(tempOneHostStr);
                }
            }
        }
        if (handlerParams.length > INPUT_NAME_INDEX) {
            inputName = handlerParams[INPUT_NAME_INDEX];
            operatorDefinitionList = parseOperatorDefinition(container,
                    handlerParams, INPUT_NAME_INDEX + 1);
        }
    }

    protected String extractAndComputeFullInput(HyperspaceDuplexFlowContext duplexFlowContext,
            String inputName, List<OperatorDefinition> operatorDefinitionList) {
        String inputValue;
        if (URI_CONFIG_PARAM_NAME.equals(inputName)) {
            inputValue = duplexFlowContext.getUri();
        } else if (IP_CONFIG_PARAM_NAME.equals(inputName)) {
            inputValue = duplexFlowContext.getClientIp();
        } else if (BODY_PARAM_NAME.equals(inputName)) {
            inputValue = new String(duplexFlowContext.getEntryProtocolAdapter()
                    .getEntryBody(duplexFlowContext), Charsets.UTF_8);
        } else {
            inputValue = duplexFlowContext.getEntryProtocolAdapter()
                    .getEntryHeader(duplexFlowContext, inputName );
        }
        if (!CollectionUtils.isEmpty(operatorDefinitionList)) {
            for (OperatorDefinition operatorDefinition : operatorDefinitionList) {
                inputValue = operatorDefinition.compute(inputValue);
                if (inputValue == null) {
                    return null;
                }
            }
        }
        return inputValue;
    }

}
